Action.special = (function(){
    function doGray(){
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = PP.context.createImageData(w, h),
            inputData = input.data,
            outputData = output.data;
        for (var i = 0; i< h; i++){
            for (var j = 0; j< w; j++){
                var pixel = (i*w + j)*4;
                var factor = ((inputData[pixel] *.3 + inputData[pixel+1]*.59 + inputData[pixel+2]*.11) );
                outputData[pixel] = factor * 1;
                outputData[pixel+1] = factor * 1;
                outputData[pixel+2] = factor * 1;
                outputData[pixel+ 3] = inputData[pixel+ 3];
            }
        }
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }
    
    function doInvert(){
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = PP.context.createImageData(w, h),
            inputData = input.data,
            outputData = output.data;
        for (var i = 0; i < h; i++) {
            for (var j = 0; j < w; j++) {
                var pixel = (i*w + j)*4;
                for (var c = 0; c < 3; c++) {         
                    outputData[pixel+c] = 255 - inputData[pixel+c];
                }
                outputData[pixel+ 3] = inputData[pixel+ 3];
            }   
        }
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function doWatermark(){
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = PP.context.createImageData(w, h),
            inputData = input.data,
            outputData = output.data;
        for (var i = 0; i < h; i++) {
            for (var j = 0; j < w; j++) {
                var pixel = (i*w + j)*4;
                var factor = ((inputData[pixel] *.3 + inputData[pixel+1]*.59 + inputData[pixel+2]*.11) );
                
                outputData[pixel] = factor;
                outputData[pixel+1] = factor;
                outputData[pixel+2] = factor;
                outputData[pixel+ 3] = 32;
            }   
        }
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function doEdgeDetection() {
        var output = _doEdgeDetectionAlgorithm(127, 0);
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function doNeonLines(){
        var output = _doEdgeDetectionAlgorithm(0, 0);
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function _doEdgeDetectionAlgorithm(baseColor, grey) {
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = PP.context.createImageData(w, h),
            inputData = input.data,
            outputData = output.data;
        var bytesPerRow = w * 4,
            pixel = bytesPerRow + 4, // Start at (1,1)
            hm1 = h - 1,
            wm1 = w - 1,
            d = new Date;
        for (var y = 1; y < hm1; ++y) {
            var centerRow = pixel - 4;
            var priorRow = centerRow - bytesPerRow;
            var nextRow = centerRow + bytesPerRow;
            var r1 = - inputData[priorRow]   - inputData[centerRow]   - inputData[nextRow];
            var g1 = - inputData[++priorRow] - inputData[++centerRow] - inputData[++nextRow];
            var b1 = - inputData[++priorRow] - inputData[++centerRow] - inputData[++nextRow];
            var rp = inputData[priorRow += 2];
            var rc = inputData[centerRow += 2];
            var rn = inputData[nextRow += 2];
            var r2 = - rp - rc - rn;
            var gp = inputData[++priorRow];
            var gc = inputData[++centerRow];
            var gn = inputData[++nextRow];
            var g2 = - gp - gc - gn;
            var bp = inputData[++priorRow];
            var bc = inputData[++centerRow];
            var bn = inputData[++nextRow];
            var b2 = - bp - bc - bn;
            
            // Main convolution loop
            for (var x = 1; x < wm1; ++x) {
                centerRow = pixel + 4;
                priorRow = centerRow - bytesPerRow;
                nextRow = centerRow + bytesPerRow;
                var r = baseColor + r1 - rp - (rc * -8) - rn;
                var g = baseColor + g1 - gp - (gc * -8) - gn;
                var b = baseColor + b1 - bp - (bc * -8) - bn;
                r1 = r2;
                g1 = g2;
                b1 = b2;
                rp = inputData[priorRow];
                rc = inputData[centerRow];
                rn = inputData[nextRow];
                r2 = - rp - rc - rn;
                gp = inputData[++priorRow];
                gc = inputData[++centerRow];
                gn = inputData[++nextRow];
                g2 = - gp - gc - gn;
                bp = inputData[++priorRow];
                bc = inputData[++centerRow];
                bn = inputData[++nextRow];
                b2 = - bp - bc - bn;
                if( !grey ) {
                    outputData[pixel] = r + r2;
                    outputData[++pixel] = g + g2;
                    outputData[++pixel] = b + b2;
                } else {
                    factor = .3* (r+r2) + .59*(g+g2) +.11*(b+b2);
                    outputData[pixel] = factor;
                    outputData[++pixel] = factor;
                    outputData[++pixel] = factor;
                }
                outputData[++pixel] = 255; // alpha
                ++pixel;
            }
            pixel += 8;
        }
        //alert((new Date - d) / 1000);
        return output;
    }

    function doBlackAndWhite(){
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = PP.context.createImageData(w, h),
            inputData = input.data,
            outputData = output.data;
        for (var i = 0; i< h; i++){
            for (var j = 0; j< w; j++){
                var pixel = (i*w + j)*4;
                var factor = ((inputData[pixel] *.3 + inputData[pixel+1]*.59 + inputData[pixel+2]*.11) );
                if( factor > 192 ) 
                    factor = 255;
                else
                    factor = 0;
                for (var c = 0; c < 3; c += 1) {         
                    outputData[pixel+c] = factor;
                }
                outputData[pixel+ 3] = inputData[pixel+ 3];
            }
        }
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function doColorBook(){
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = _doEdgeDetectionAlgorithm(255,1),
            inputData = input.data,
            outputData = output.data;
        var pixel = 0;
        for (var i = 0; i< h; i++){
            for (var j = 0; j< w; j++){
                if( outputData[pixel] < 240 ) {
                    outputData[pixel] = 0;
                    outputData[++pixel] = 0;
                    outputData[++pixel] = 0;
                } else {
                    outputData[pixel] = 255;
                    outputData[++pixel] = 255;
                    outputData[++pixel] = 255;
                }
                outputData[++pixel] = 255;
                ++pixel;
            }
        }
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function doRipples() {
        var output = _doDisplacement(_getRippleData(), .2, 1, 1, 128, 128);
        PP.context.clearRect(0,0,PP.WIDTH,PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function _getRippleData() {
        var cv2 = document.createElement("canvas");
        var width = PP.w;
        var height = PP.h;
        cv2.height = height;
        cv2.width = width;
        var cvctx = cv2.getContext('2d');
        
        var radgrad = cvctx.createRadialGradient(182,182,1,256,256,500);
        radgrad.addColorStop(0.0, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.05, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.1, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.15, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.2, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.25, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.3, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.35, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.4, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.45, 'rgba(255,0,0,1)');     
        radgrad.addColorStop(0.5, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.55, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.6, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.65, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.7, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.75, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.8, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.85, 'rgba(255,0,0,1)');
        radgrad.addColorStop(0.9, 'rgba(255,255,0,1)');
        radgrad.addColorStop(0.95, 'rgba(255,0,0,1)');
        radgrad.addColorStop(1, 'rgba(255,255,0,1)');
        
        cvctx.fillStyle = radgrad;
        cvctx.fillRect(0,0,width,height);
        
        return cvctx;
    }
    function _doDisplacement(displacementCanvas, scale, xChannel, yChannel, xOffset, yOffset) {
        var w = PP.w,
            h = PP.h,
            input = PP.context.getImageData(PP.x, PP.y, w, h),
            output = PP.context.createImageData(w, h),
            inputData = input.data,
            outputData = output.data;

        var displacementMap = displacementCanvas.getImageData(0, 0, w, h);
        var displacementMapData = displacementMap.data;
        var d = new Date;
        var bytesPerRow = w * 4;
        var pixel = 0;
        if( xChannel == yChannel && xOffset==yOffset ) {
            for (var y = 0; y < h; ++y) {
                for (var x = 0; x < w; ++x) {
                    var displacement = (scale * (displacementMapData[pixel + xChannel]-xOffset)) | 0;
                    var j = ((displacement + y) * w + x + displacement)*4;
                    outputData[pixel]   = inputData[j];
                    outputData[++pixel] = inputData[++j];
                    outputData[++pixel] = inputData[++j];
                    outputData[++pixel] = inputData[++j];
                    ++pixel;
                }
            }
        } else {
            for (var y = 0; y < h; ++y) {
                for (var x = 0; x < w; ++x) {
                    var displacementx = (scale * (displacementMapData[pixel + xChannel]-xOffset)) | 0;
                    var displacementy = (scale * (displacementMapData[pixel + yChannel]-yOffset)) | 0;
                    var j = ((displacementx + y) * w + x + displacementy)*4;
                    outputData[pixel]   = inputData[j];
                    outputData[++pixel] = inputData[++j];
                    outputData[++pixel] = inputData[++j];
                    outputData[++pixel] = inputData[++j];
                    ++pixel;
                }
            }
        }
        return output;
    }
    
    function _getGlassBoxData() {
        var cv4 = document.createElement("canvas"),
            bulge = document.getElementById("glassBoxImage"),
            height = bulge.height * 2,
            width = bulge.width * 2;
        cv4.height = height;
        cv4.width = width;
        
        var cvctx = cv4.getContext('2d');
            
        cvctx.drawImage(bulge, 0, 0, width, height);
        return cv4;
    }

    function _doPattern(pattern, scale, xChannel, yChannel, yOffset, xOffset) {
        var w = PP.w,
            h = PP.h;

        var cv = document.createElement("canvas");
        cv.width = w;
        cv.height = h;
        var cvctx = cv.getContext('2d');
        var ptrn = cvctx.createPattern(pattern,'repeat');
        cvctx.fillStyle = ptrn;
        cvctx.fillRect(0,0,w,h);
        
        return _doDisplacement(cvctx, scale, xChannel, yChannel, yOffset, xOffset);
    }
    
    function doGlassBox() {
        var output = _doPattern(_getGlassBoxData(), .6, 0, 1,160,160);
        PP.context.clearRect(0, 0, PP.WIDTH, PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }
    
    function _getBulgeData() {
        var cv3 = document.createElement("canvas");
        var width = PP.w;
        var height = PP.h;
        cv3.width = width;
        cv3.height = height;
        var cvctx = cv3.getContext('2d');
          
        bulge = new Image();
        bulge.src = "img/sphere.png";

        
        cvctx.drawImage(bulge, 0, 0, width, height);
        return cvctx;
    }

    function doBulge() {
        var output = _doDisplacement(_getBulgeData(), 1, 1, 0,176,176);
        PP.context.clearRect(0, 0, PP.WIDTH, PP.HEIGHT);
        PP.context.putImageData(output, PP.x, PP.y);
    }

    function init(){
        var btns = $("#toolBox>div>div:nth-of-type(2) li");
        $(btns[0]).click(doGray);
        $(btns[1]).click(doInvert);
        $(btns[2]).click(doWatermark);
        $(btns[3]).click(doEdgeDetection);
        $(btns[4]).click(doNeonLines);
        $(btns[5]).click(doBlackAndWhite);
        $(btns[6]).click(doColorBook);
        $(btns[7]).click(doRipples);
        $(btns[8]).click(doGlassBox);
        $(btns[9]).click(doBulge);
    }

    return {init:init};
})();